// Author: Long Nguyen
// CS 5600 - Spring 2010
// Lab 5: Texture Map
// Date: 03/10/2010


#ifdef __APPLE__
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#include <GLUT/glut.h>
#elif defined(_WIN32)
#include <windows.h>
#include <GL/glut.h>
#include "glm.h"
#else
#include <cstdlib>
#include <GL/glut.h>
#endif

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

#include <cstdio>
#include <cmath>

using namespace std;

/* Global Variables */

int _main_window;
int _pos_x = 50;
int _pos_y = 50;
int _width = 1000;
int _height = 900;

const char* _window_title = "Lab 5: Texture Map";

// Scene Rotation
float _rot_angle_scene = 0.0;
float _rot_scene_step = 0.1;

// Scene Translation
enum TRANSLATION_MODE {TRANS_RIGHT, TRANS_UP, TRANS_LEFT, TRANS_DOWN, TRANS_IN, TRANS_OUT};

TRANSLATION_MODE _trans_mode = TRANS_RIGHT;
float _trans_scene_vector[3] = {-25, 0, -25};
float _trans_scene_step = 0.1;

// Scene Animation Toggle
bool _bToggleAnimation = true;
bool _isLightFixed = true;			// toggle between fixed and moving light

// Mouse positions
int _oldMouseX;
int _oldMouseY;

float _deltaX = 0;
float _deltaY = 0;
float _mouseStep = 1000;

// Image Texture 
GLint _textureMode = GL_MODULATE;
GLuint _texID;
unsigned char* _image = NULL;


GLfloat _envColor[] = {1, 0, 1, 1};					// environment color for texture blending/modulating

GLfloat	LightPos[] = {10, 50, 10, 1.0f};			// Light Position, 1: positional

GLfloat brass_am[] = {0.329412, 0.223529, 0.027451, 1.0};
GLfloat brass_diff[] = {0.780392, 0.568627, 0.113725, 1.0};
GLfloat brass_spec[] = {0.992157, 0.941176, 0.807843, 1.0};
GLfloat brass_shi[] = {27.8974};

GLfloat polSilver_am[] = {0.23125, 0.23125, 0.23125, 1.0};
GLfloat polSilver_diff[] = {0.2775, 0.2775, 0.2775, 1.0};
GLfloat polSilver_spec[] = {0.773911, 0.773911, 0.773911, 1.0};
GLfloat polSilver_shi[] = {89.6};

GLfloat gold_am[] = {0.24725, 0.1995, 0.0745, 1.0};
GLfloat gold_diff[] = {0.75164, 0.60648, 0.22648, 1.0};
GLfloat gold_spec[] = {0.628281, 0.555802, 0.366065, 1.0};
GLfloat gold_shi[] = {51.2};

GLfloat bronze_am[] = {0.2125, 0.1275, 0.054, 1.0};
GLfloat bronze_diff[] = {0.714, 0.4284, 0.18144, 1.0};
GLfloat bronze_spec[] = {0.393548, 0.271906, 0.166721, 1.0};
GLfloat bronze_shi[] = {25.6};


/* Function Declaration */

// GLUT Callback Functions
void idle();
void keyboard(unsigned char key, int x, int y);
void reshape(int width, int height);
void display();
void mousePressed(int button, int state, int x, int y);
void mouseDragged(int x, int y);

// Initialization Functions
void initGL();

// Drawing functions
void drawGround();
void drawCylinder();
void loadTexture();
void renderScene();

/* Program Main Entry Function */

int main(int argc,	char* argv[])
{
	//
	// create the glut window
	//
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutInitWindowSize(_width, _height);
	glutInitWindowPosition(_pos_x, _pos_y);
	_main_window = glutCreateWindow(_window_title);

	// GLUT Callback Functions
	glutDisplayFunc(display);
	glutKeyboardFunc(keyboard);
	glutReshapeFunc(reshape);
	glutIdleFunc(idle);
	glutMouseFunc(mousePressed);
	glutMotionFunc(mouseDragged);

	// Initialize GL
	initGL();

	// give control over to glut
	glutMainLoop();
	return 0;
}


void initGL()
{	
	// Light Parameters
	GLfloat	LightAmb[] = {0.7f, 0.7f, 0.7f, 1.0f};			// Ambient Light
	GLfloat	LightDif[] = {0.9f, 0.9f, 0.9, 1.0f};			// Diffuse Light
	
	//GLfloat	LightPos[] = {0, 200, 200, 0};				// Infinitely far away light source

	glClearColor(0,0,0,0);			//Clear background to black 	
	glShadeModel(GL_SMOOTH);		// Use smooth shading.
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);			// Really Nice Perspective Calculations

	glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmb);				// Set The Ambient Lighting For Light0
	glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDif);				// Set The Diffuse Lighting For Light0
	glLightfv(GL_LIGHT0, GL_SPECULAR, LightDif);			// Set The Specular Lighting For Light0
			
	glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
	//glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 30);
	//glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 2);			// enable lighting linear attenuation
	//glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 1);			// enable lighting linear attenuation

	glEnable(GL_LIGHT0);							// Enable Light 0
	glEnable(GL_LIGHTING);							// Enable Lighting	
	glEnable(GL_DEPTH_TEST);						// Enable Depth Test
	glEnable(GL_NORMALIZE);							// auto renormalize surface normals
	
	loadTexture();			
}


void idle(void)
{
	if (glutGetWindow() != _main_window)
		glutSetWindow(_main_window);

	// Update Scene Rotation Angle
	_rot_angle_scene += _rot_scene_step;
	_rot_angle_scene = fmodf(_rot_angle_scene, 360.f);

	// Update Scene Translation
	if (_trans_scene_vector[0] > 25)		// Right side of rectangle
	{
		_trans_mode = TRANS_IN;		
	}
	else if (_trans_scene_vector[0] < -25)	// Left side of rectangle
	{
		_trans_mode = TRANS_OUT;		
	}
	else if (_trans_scene_vector[2] > 25)	// Top side of rectangle
	{
		_trans_mode = TRANS_RIGHT;
	}
	else if (_trans_scene_vector[2] < -25)	// Bottom side of rectangle
	{
		_trans_mode = TRANS_LEFT;
	}

	switch(_trans_mode)
	{
	case TRANS_RIGHT:
		_trans_scene_vector[0] += _trans_scene_step;
		_trans_scene_vector[2] = 25;
		break;
	case TRANS_IN:
		_trans_scene_vector[0] = 25;
		_trans_scene_vector[2] -= _trans_scene_step;		
		break;
	case TRANS_LEFT:
		_trans_scene_vector[0] -= _trans_scene_step;
		_trans_scene_vector[2] = -25;
		break;	
	case TRANS_OUT:
		_trans_scene_vector[0] = -25;
		_trans_scene_vector[2] += _trans_scene_step;
		break;
	}

	// Keep redrawing the scene
	glutPostRedisplay();
}


void keyboard(unsigned char key, int x, int y)
{
	switch(key)
	{
	// Light stays stationary or moving with eye.
	case 'c':
	case 'C':
		_isLightFixed = !_isLightFixed;
		break;

	// Reduce scene rotation speed
	case 's':
	case 'S':
		_rot_scene_step /= 1.1;
		break;

	// Increase scene rotation speed
	case 'f':
	case 'F':
		_rot_scene_step *= 1.1;
		break;
	
	// enable FLAT SHADING
	case 'q':
	case 'Q':
		glShadeModel(GL_FLAT);
		break;
	

	// enable SMOOTH SHADING
	case 'w':
	case 'W':
		glShadeModel(GL_SMOOTH);
		break;	

	// enable REPLACE mode
	case 'r':
	case 'R':
		_textureMode = GL_REPLACE;
		break;

	// enable MODULATE mode
	case 'm':
	case 'M':
		//_textureMode = GL_BLEND;
		_textureMode = GL_MODULATE;
		break;

	// Toggle Pausing of Scene Animation
	case 'p':
	case 'P':
		if (_bToggleAnimation) {
			// Animating: Pause the animation
			glutIdleFunc(NULL);
		}
		else {
			// Not Animating: Start the animation
			glutIdleFunc(idle);
		}
		_bToggleAnimation = !_bToggleAnimation;
		break;

	// quit
	case 27: 
		exit(EXIT_SUCCESS);
		break;
	}	
	glutPostRedisplay();
}

void mousePressed(int button, int state, int x, int y) {

	if ( state == GLUT_DOWN ) {
		_oldMouseX = x;
		_oldMouseY = y;
		//printf("Mouse Down x = %d y = %d \n", x, y);
	}
}

void mouseDragged(int x, int y) {
	//printf("Mouse Dragged x = %d y = %d \n", x, y);

	if ( abs(x - _oldMouseX) > abs(y - _oldMouseY) ) {		// x direction
		_deltaX += (x - _oldMouseX) * 360 / _mouseStep;
	}
	else if ( abs(x - _oldMouseX) < abs(y - _oldMouseY) ) {	// y direction
		_deltaY += (y - _oldMouseY) * 360 / _mouseStep;
	}
	else {
		_deltaX += (x - _oldMouseX) * 360 / _mouseStep;
		_deltaY += (y - _oldMouseY) * 360 / _mouseStep;
	}
}

void reshape(int width, int height)
{
	_width = width;
	_height = height;
	glViewport(0, 0, _width, _height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(90, static_cast<double>(_width) / static_cast<double>(_height), 0.01, 1000);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glutPostRedisplay();
}

void display( )
{	
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glClearColor(0,0,0,0);		// Clear background to black
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();		

	//glColor3f(0, 1, 0);		// green-ish texture
	glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, _envColor);
		
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, _texID);
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, _textureMode);		

	// Set the camera location
	//gluLookAt(-10, 80, 60,			// eye
	//			0, 5, 0,			// center
	//			0, 1, 0);			// up	

	glRotatef(45, 1, 0, 0);
	glTranslatef(10, -80, -60);	

	//glPushMatrix();
		renderScene();		
	//glPopMatrix();		
		
	if (_isLightFixed) {			// Light source moves with eye or stays fixed
		glLoadIdentity();		
	}
	glLightfv(GL_LIGHT0, GL_POSITION, LightPos);			// Set The Position For Light0		

	glutSwapBuffers();
	glDisable(GL_TEXTURE_2D);
}

// Draws objects in scene.
//
void renderScene() {			
	
	// Ground plane
	//drawGround();

	glRotatef(_deltaX, 0, 1, 0);
	glRotatef(_deltaY, 1, 0, 0);
		
	// Rotate whole scene
	glRotatef(_rot_angle_scene, 0, 1, 0);

	// Translate the whole scene
	//glTranslatef(_trans_scene_vector[0], _trans_scene_vector[1], _trans_scene_vector[2]);
		
	drawCylinder();
}

void drawCylinder() {
	
	float radius = 20;
	float angle = 36;	
	int height = 60;
	// a^2 = b^2 + c^2 - 2(bc)cos(a)	

	int verticesX[10];
	int verticesZ[10];
	float texS[10];

	float angleStart = angle/2;
	float angleR;
	float step = 0.1;
	
	for (int i=0; i<10; i++) {
		angleR = angleStart*M_PI/180;
		verticesX[i] = radius * cos(angleR);
		verticesZ[i] = radius * sin(angleR) * (-1);
		angleStart += angle;
		texS[i] = step * i;
	}	

	glPushMatrix();
						
		// draw body of the can
		//
		glBegin(GL_QUADS);
			for (int j=0; j<9; j++) {
				//glTexCoord2f(0, 0);
				glTexCoord2f(texS[j], 0);
				glVertex3f(verticesX[j], height, verticesZ[j]);
				//glTexCoord2f(0, 1);
				glTexCoord2f(texS[j], 1);
				glVertex3f(verticesX[j], 0, verticesZ[j]);
				//glTexCoord2f(1, 1);
				glTexCoord2f(texS[j+1], 1);
				glVertex3f(verticesX[j+1], 0, verticesZ[j+1]);
				//glTexCoord2f(1, 0);
				glTexCoord2f(texS[j+1], 0);
				glVertex3f(verticesX[j+1], height, verticesZ[j+1]);
				
			}
			//glTexCoord2f(0, 0);
			glTexCoord2f(texS[9], 0);
			glVertex3f(verticesX[9], height, verticesZ[9]);
			//glTexCoord2f(0, 1);
			glTexCoord2f(texS[9], 1);
			glVertex3f(verticesX[9], 0, verticesZ[9]);
			//glTexCoord2f(1, 1);
			glTexCoord2f(1, 1);
			glVertex3f(verticesX[0], 0, verticesZ[0]);
			//glTexCoord2f(1, 0);
			glTexCoord2f(1, 0);
			glVertex3f(verticesX[0], height, verticesZ[0]);									
		glEnd();
		//glBegin(GL_QUAD_STRIP);
		//	for (int j=0; j<10; j++) {
		//		glTexCoord2f(texS[j], 1);
		//		glVertex3f(verticesX[j], height, verticesZ[j]);
		//		glTexCoord2f(texS[j], 0);
		//		glVertex3f(verticesX[j], 0, verticesZ[j]);						
		//	}
		//	glTexCoord2f(texS[0], 1);
		//	glVertex3f(verticesX[0], height, verticesZ[0]);
		//	glTexCoord2f(texS[0], 0);
		//	glVertex3f(verticesX[0], 0, verticesZ[0]);
		//glEnd();

		glDisable(GL_TEXTURE_2D);		
		// Draw top and bottom lids
		//
		glColor3f(1, 1, 1);				// White lids
		glBegin(GL_POLYGON);						
			for (int j=0; j<10; j++) {
				glVertex3f(verticesX[j], height, verticesZ[j]);								
			}			
			glVertex3f(verticesX[0], height, verticesZ[0]);
		glEnd();
		glBegin(GL_POLYGON);
			for (int j=0; j<10; j++) {				
				glVertex3f(verticesX[j], 0, verticesZ[j]);						
			}			
			glVertex3f(verticesX[0], 0, verticesZ[0]);
		glEnd();
		//glBegin(GL_TRIANGLE_FAN);
		//	glVertex3f(0, height, 0);
		//	for (int j=0; j<10; j++) {
		//		glVertex3f(verticesX[j], height, verticesZ[j]);								
		//	}
		//	glVertex3f(verticesX[0], height, verticesZ[0]);
		//glEnd();				

		glEnable(GL_TEXTURE_2D);
	glPopMatrix();
}

void drawGround() {

	glMaterialfv(GL_FRONT, GL_AMBIENT, bronze_am);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, bronze_diff);
	glMaterialfv(GL_FRONT, GL_SPECULAR, bronze_spec);
	glMaterialfv(GL_FRONT, GL_SHININESS, bronze_shi);

	glBegin(GL_QUADS);
		// vertices in counter-clockwise order
		glVertex3f(500, 0, 500);
		glVertex3f(500, 0, -500);
		glVertex3f(-500, 0, -500);
		glVertex3f(-500, 0, 500);				
	glEnd();
}

void loadTexture() {

	char* imageFile = "coke_LN.ppm";	
	int imageH, imageW;	

	if (_image) {
		free(_image);
	}
	_image = glmReadPPM( imageFile, &imageW, &imageH );

	printf("Image width = %d height = %d \n", imageW, imageH);
	if (!_image) {
		printf( "Error loading image \n" );
	}	
	else {
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
		glGenTextures(1, &_texID);
		glBindTexture(GL_TEXTURE_2D, _texID);		
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);		
		//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);		

		// Use RGB instead of RGBA
		//
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, imageW, imageH, 0, GL_RGB, GL_UNSIGNED_BYTE, _image);
	}
}

